#include "SPHY.h"
#include "Poco/UUIDGenerator.h"
#include "../../Log/NetLog.h"
#include "SLLE.h"
#include "Poco/MD5Engine.h"

using namespace std;

SPHY* SPHY::m_pInstance = NULL;

SPHY* SPHY::Initialize()
{
    return GetInstance();
}

SPHY* SPHY::GetInstance()
{
    if (m_pInstance == NULL)
    {
        static Poco::Mutex s_mutex;
        Poco::Mutex::ScopedLock lock(s_mutex);
        if (m_pInstance == NULL)
        {
            m_pInstance = new SPHY;
        }
    }
    return m_pInstance;
}

int SPHY::Start()
{
    try
    {
        if (m_bStop == true)
        {
            //m_ready.set();
            m_thread.start(*this);
            m_bStop = false;
            //m_ready.wait();
        }
        return 0;
    }
    catch (...)
    {
        g_NetLog.Error("catched\n");
        return -1;
    }
}

int SPHY::Stop()
{
    try
    {
        if (m_bStop == false)
        {
            m_bStop = true;
            m_thread.join();
        }
        return 0;
    }
    catch (...)
    {
        g_NetLog.Error("catched\n");
        return -1;
    }
}

int SPHY::Write(string strSLLEData)
{
    string strMd5 = MakeMd5(strSLLEData);
    int iTimes = 0;
    do
    {
        string strSPHY = CompileWriteData(strSLLEData, strMd5, iTimes);
        int iCnt = SerialWrite(strSPHY);
        if (iCnt < strSPHY.size())
        {
            g_NetLog.Debug("gj iCnt [%d] < strSPHY.size [%d]\n", iCnt, strSPHY.size());
            Poco::Thread::sleep(10);
        }
        else if (HasReadMd5(strMd5))
        {
            return iCnt;
        }
        iTimes += 1;
        g_NetLog.Debug("gj serial ReWriteNoSleep [%s]\n", strSPHY.c_str());
        Poco::Thread::sleep(10);
    } while (iTimes < 10);
    return -1;
}

std::string SPHY::Read()
{
    Poco::Mutex::ScopedLock lock(m_mutexRecvData);
    if (m_vectRecvData.empty())
    {
        return "";
    }
    std::string str = m_vectRecvData[0];
    
    int iFrameLen = GetFrameLen(str);
    int iLenLen = GetLenLen(iFrameLen);
    str.erase(str.end() - 38, str.end());
    str.erase(0, 8 + iLenLen);
    m_vectRecvData.erase(m_vectRecvData.begin());
    return str;
}

SPHY::SPHY()
{
    m_bStop = true;
    m_pSerialChn = GetSerialChannel(MainConfig::GetInstance()->HighGradeModule_GetHttpRetransSerialNumber());
    if (m_pSerialChn)
    {
        //m_pSerialChn->St
    }
}

SPHY::~SPHY()
{

}

void SPHY::run()
{
    while (!m_bStop || 1)
    {
        if (!m_pSerialChn)
        {
            return;
        }
        Routine();
        Poco::Thread::sleep(10);
    }
}

void SPHY::Routine()
{
    try
    {
        char buf[MAX_SPHY_READ_LENGTH] = { 0 };
        m_pSerialChn->ReadNotBolck(buf, MAX_SPHY_READ_LENGTH - 1, 1000);
        if (strlen(buf) != 0)
        {
            g_NetLog.Debug("gj serial readnoblock [%s]\n", buf);
            DealSerialData(buf);
        }
        Poco::Thread::sleep(10);
    }
    catch (...)
    {
        g_NetLog.Error("catched\n");
    }
}

SerialChannel* SPHY::GetSerialChannel(string strName)
{
    std::vector<SerialChannel*> vectpSerialChannel = SerialModule::GetInstance()->GetVectChannels();

    for (int i = 0; i < (int)vectpSerialChannel.size(); i++)
    {
        if (vectpSerialChannel[i]->Name() == strName)
        {
            return vectpSerialChannel[i];
        }
    }
    return NULL;
}

string SPHY::MakeMd5(string strData)
{
    try
    {
        Poco::MD5Engine md5Engine;
        md5Engine.update(strData);
        Poco::MD5Engine::Digest dig = md5Engine.digest();
        return Poco::MD5Engine::digestToHex(dig);
    }
    catch (...)
    {
        g_NetLog.Error("catched\n");
        return "";
    }
}

string SPHY::CompileWriteData(string strData, string strMd5, int iTimes)
{
    return string("<s,w,") + SLLE::GetInstance()->Int2String(strData.size()) + string(",") +
        SLLE::GetInstance()->Int2String(iTimes) + string(",") + strData + string(",") + strMd5 + string(",w,s>");
}

std::string SPHY::CompileRecvData(std::string strData, std::string strMd5, int iTimes)
{
    return string("<s,r,") + SLLE::GetInstance()->Int2String(strData.size()) + string(",") +
        SLLE::GetInstance()->Int2String(iTimes) + string(",") + strData + string(",") + strMd5 + string(",r,s>");
}

bool SPHY::HasReadMd5(string strMd5)
{
    try
    {
        Poco::Clock clk;
        do
        {
            Poco::Thread::sleep(20);
            Poco::Mutex::ScopedLock lock(m_mutexRecvData);
            for (size_t i = 0; i < m_vectRecvData.size(); ++i)
            {
                string str = m_vectRecvData[i];
                if (str.find(string("<s,r,")) == 0 && str.find(strMd5) != string::npos)
                {
                    m_vectRecvData.erase(m_vectRecvData.begin() + i);
                    return true;
                }
            }
        } while (clk.elapsed() < 200 * 1000);
        g_NetLog.Debug("gj don't has MD5 [%s]\n", strMd5.c_str());
        return false;
    }
    catch (...)
    {
        g_NetLog.Error("catched\n");
        return false;
    }
}

void SPHY::DealSerialData(string strData)
{
    int iSH = strData.find(string("<s,"));
    int iST = strData.find(string(",s>"));
    if (m_strRecvData.empty())
    {
        if (iSH != string::npos && iST != string::npos)
        {
            if (iSH < iST)
            {
                string strTmp = strData.substr(iSH, iST + 3 - iSH);
                if (CheckData(strTmp))
                {
                    SaveData(strTmp);
                    if (strTmp.find(string("<s,w,")) != string::npos && strTmp.find(string(",w,s>")) != string::npos)
                    {
                        string strMd5 = MakeMd5(m_strMd5);
                        string strRes = CompileRecvData(m_strMd5, strMd5, 0);
                        SerialWrite(strRes);
                    }
                }
                strData.erase(0, iST + 3);
                DealSerialData(strData);
            }
            else
            {
                strData.erase(0, iSH);
                DealSerialData(strData);
            }
        }
        else if (iSH != string::npos)
        {
            m_strRecvData.assign(strData.begin() + iSH, strData.end());
        }
        /*else if (iST != string::npos)
        {
            //nothing;
        }
        else
        {
            ;//nothing
        }*/
    }
    else
    {
        int iFrameLen = GetFrameLen(m_strRecvData);
        if (iFrameLen == -1)
        {
            string::iterator strDataBegin = strData.begin();
            m_strRecvData.append(strDataBegin, strDataBegin);
            strData.erase(strDataBegin);
            DealSerialData(strData);
        }
        else if (iFrameLen == -2)
        {
            g_NetLog.Error("is lose bytes error0 m_strRecvData[%s]\n", m_strRecvData.c_str());
            g_NetLog.Error("is lose bytes error1 m_strData[%s]\n", strData.c_str());
            m_strRecvData.clear();
            DealSerialData(strData);
        }
        else if (iFrameLen == -3)
        {
            g_NetLog.Error("unkonw error0 m_strRecvData[%s]\n", m_strRecvData.c_str());
            g_NetLog.Error("unkonw error1 m_strData[%s]\n", strData.c_str());
            m_strRecvData.clear();
            DealSerialData(strData);
        }
        else if (iFrameLen > MAX_SPHY_LENGTH)
        {
            g_NetLog.Error("is lose bytes error2 m_strRecvData[%s]\n", m_strRecvData.c_str());
            g_NetLog.Error("is lose bytes error3 m_strData[%s]\n", strData.c_str());
            m_strRecvData.clear();
            DealSerialData(strData);
        }
        else
        {
            if (iST != string::npos && iSH == string::npos)
            {
                m_strRecvData.append(strData.begin(), strData.begin() + (iST + 3));
                if (CheckData(m_strRecvData))
                {
                    SaveData(m_strRecvData);
                    if (m_strRecvData.find(string("<s,w,")) != string::npos && m_strRecvData.find(string(",w,s>")) != string::npos)
                    {
                        string strMd5 = MakeMd5(m_strMd5);
                        string strRes = CompileRecvData(m_strMd5, strMd5, 0);
                        SerialWrite(strRes);
                    }
                }
                else
                {
                    ;//nothing "<s,w,len,times,uuid,total,n,xxxx,w,s>xxx,w,s>"
                }
                strData.erase(0, iST);
                m_strRecvData.clear();
            }
            else if (iST != string::npos && iSH != string::npos && iSH < iST)
            {
                m_strRecvData.clear();
                DealSerialData(strData);
            }
            else
            {
                m_strRecvData.append(strData);
            }
        }
    }

    if (m_strRecvData.size() > MAX_SPHY_LENGTH)
    {
        m_strRecvData.clear();
    }
}

bool SPHY::CheckData(std::string strData)
{
    m_strMd5.clear();
    int iFrameLen = GetFrameLen(strData);
    int iLenLen = GetLenLen(iFrameLen);
    if (iFrameLen + 8 + iLenLen + 38 == strData.size())
    {
        string strMd5 = MakeMd5(strData.substr(8 + iLenLen, iFrameLen));
        if (strData.find(strMd5, iFrameLen) != string::npos)
        {
            m_strMd5 = strMd5;
            return true;
        }
    }
    return false;
}

int SPHY::SaveData(std::string strData)
{
    Poco::Mutex::ScopedLock lock(m_mutexRecvData);
    m_vectRecvData.push_back(strData);
    return m_vectRecvData.size();
}

int SPHY::GetFrameLen(string m_strData)
{
    if (m_strData.find(string("<s,w,")) != string::npos || m_strData.find(string("<s,r,")) != string::npos)
    {
        int iIndex = m_strData.find(string(","), 5);
        if (iIndex != string::npos)
        {
            if (iIndex <= 8)
            {
                return SLLE::String2Int(m_strData.substr(5, iIndex));
            }
            else
            {
                return -2;
            }
        }
        else
        {
            return -1;
        }
    }
    else
    {
        return -1;
    }
    return -3;
}

int SPHY::GetLenLen(int iFrameLen)
{
    int iLenLen = 1;
    if (iFrameLen > 999)
    {
        iLenLen = 4;
    }
    else if (iFrameLen > 99)
    {
        iLenLen = 3;
    }
    else if (iFrameLen > 9)
    {
        iLenLen = 2;
    }
    else
    {
        ;
    }
    return iLenLen;
}

int SPHY::SerialWrite(std::string strData)
{
    if (!m_pSerialChn)
    {
        g_NetLog.Error("serial is NULL\n");
        return -1;
    }
    if (strData.size() > MAX_SPHY_LENGTH)
    {
        g_NetLog.Error("too long to write\n");
        return -1;
    }
    Poco::Mutex::ScopedLock lock(m_mutexSerialWrite);
    return m_pSerialChn->WriteNoSleep((char*)strData.c_str(), strData.size());
}
